package top.lyjwn.todo.aop.log.controller;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import top.lyjwn.todo.aop.token.Login;
import top.lyjwn.todo.aop.token.Token;
import top.lyjwn.todo.common.template.R;
import top.lyjwn.todo.common.utils.CusAccessObjectUtil;

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 〈操作日志切面记录〉<br>
 * 〈使用日志切面的项目必须要实现LogService，可不做操作，但必须要实现〉
 *
 * @author luoyujie
 */
@Component
@Aspect
public class ControllerLogAspect {
    /**
     * 日志队列
     */
    //private final static ExecutorService executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors() * 2 + 1, 5, TimeUnit.SECONDS, new LinkedTransferQueue<Runnable>());

    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAspect.class);

    // 注入service,用来将日志信息保存在数据库
    @Autowired(required = false)
    private ControllerLogHandle controllerLogHandle;

    @Autowired
    private Token token;


    @Autowired
    private HttpServletRequest httpServletRequest;

    // 配置接入点,如果不知道怎么配置,可以百度一下规则
    // 指定controller的类进行切面 @Pointcut("execution(*
    // com.controller..CustomerController.*(..))||execution(*
    // com.controller.ManageController.*(..))")
    @Pointcut("bean(*Controller)")
    private void controllerAspect() {
        LOGGER.info("log aop start");
    }// 定义一个切入点

    @SuppressWarnings({"rawtypes", "unused"})
    @Around("controllerAspect()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        if (controllerLogHandle == null) {
            return pjp.proceed();
        }
        // 常见日志实体对象
        ControllerLogE controllerLogE = new ControllerLogE();
        controllerLogE = requestDispose(controllerLogE);
        // 获取登录用户账户
        HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();

        // 方法通知前获取时间,为什么要记录这个时间呢？当然是用来计算模块执行时间的
        // 获取系统时间
        controllerLogE.setCreateTime(new Date());
        // 获取系统ip,这里用的是我自己的工具类,可自行网上查询获取ip方法
        // String ip = GetLocalIp.localIp();
        // log.setIP(ip);

        // 拦截的实体类，就是当前正在执行的controller
        Object target = pjp.getTarget();
        // 拦截的方法名称。当前正在执行的方法
        String methodName = pjp.getSignature().getName();
        // 拦截的方法参数
        Object[] args = pjp.getArgs();
        // String params = Arrays.toString(pjp.getArgs());
//        JSONArray operateParamArray = new JSONArray();

        // 拦截的放参数类型
        Signature sig = pjp.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;

        Class[] parameterTypes1 = msig.getMethod().getParameterTypes();
        Object object = null;
        // 获得被拦截的方法
        Method method = msig.getMethod();
//		try {
//			method = target.getClass().getMethod(methodName, parameterTypes);
//		} catch (NoSuchMethodException e1) {
//			LOGGER.error("AopAspect around error", e1);
//		} catch (SecurityException e1) {
//			LOGGER.error("AopAspect around error", e1);
//		}
        if (null != method) {
            // 判断是否包含自定义的注解，说明一下这里的aopLog就是我自己自定义的注解
            if (method.isAnnotationPresent(ControllerLog.class)) {
                ControllerLog aopLog = method.getAnnotation(ControllerLog.class);
                controllerLogE.setDescription(aopLog.description() == null || "".equals(aopLog.description()) ? aopLog.value() : aopLog.description());
                controllerLogE.setMethod(method.getName());
                JSONObject login = token.getLogin();
                //是否登录
                if (login != null) {
                    // 从token获取登录用户
                    controllerLogE.setUserInfo(login.getString("userId"));

                } else {
                    controllerLogE.setUserInfo("not login");
                }

                // 将不能json化的参数过滤掉
                Object[] arguments = new Object[args.length];
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse
                            || args[i] instanceof MultipartFile) {
                        // ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is
                        // illegal to call this method if the current request is not in asynchronous
                        // mode (i.e. isAsyncStarted() returns false)
                        // ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException:
                        // getOutputStream() has already been called for this response
                        continue;
                    }
                    arguments[i] = args[i];
                }
                String paramter = "";
                if (arguments != null) {
                    try {
                        paramter = JSONObject.toJSONString(arguments);
                    } catch (Exception e) {
                        paramter = arguments.toString();
                    }
                }

                // 如果是登录的话,就只插入loginid
                if (aopLog.isPrivacy()) {
                    JSONArray array = JSONArray.parseArray(paramter);
                    Object obj = array.get(0);
                    String jsonString = JSONObject.toJSONString(obj);
                    try {
                        JSONObject parseObject = JSONObject.parseObject(jsonString);
                        controllerLogE.setInParam("loginId-" + parseObject.getString("userInfo"));
                        controllerLogE.setUserInfo(parseObject.getString("userInfo"));
                    } catch (Exception e) {
                        // 当参数转换不了对象的饿时候,这时意味着参数是单个的。
                        controllerLogE.setUserInfo(jsonString.replace("\"", ""));
                        controllerLogE.setInParam("loginId-" + jsonString);
                    }

                } else {
                    // 插入请求参数
                    controllerLogE.setInParam(paramter);
                }
                // 执行页面请求模块方法，开始时间
                long start = System.currentTimeMillis();
                try {

                    // 执行页面请求模块方法，并返回
                    object = pjp.proceed();
                    // 执行页面请求模块方法，结束时间，time为所用时间
                    long time = System.currentTimeMillis() - start;
                    // 记录调用时间
                    controllerLogE.setTime(time);
                    //出参
                    if (object != null) {
                        controllerLogE.setOutParam(JSONObject.toJSONString(object));
                    } else {
                        controllerLogE.setExceptionDetail("返回值为null");
                        controllerLogE.setDone(false);
                    }
                    //
                    // 将object 转化为controller封装返回的实体类：RequestResult
                    if (!aopLog.isStandard()) {
                        controllerLogE.setExceptionDetail("规范外的出参");
                        controllerLogE.setDone(true);
                    } else {
                        R r = (R) object;
                        controllerLogE.setExceptionDetail(r.getMsg());
                        controllerLogE.setDone(r.isDone());
                    }
                } catch (ClassCastException e) {
                    long time = System.currentTimeMillis() - start;
                    // 记录调用时间
                    controllerLogE.setTime(time);
                    String err = "规范外的响应方式，aop中结果转化失败：" + JSONObject.toJSONString(object) + "\r\n";
                    controllerLogE.setExceptionDetail(err);
                    controllerLogE.setDone(true);
                    //
                    LOGGER.warn(err + getStackTrace(e));
                } catch (Exception e) {
                    long time = System.currentTimeMillis() - start;
                    // 记录调用时间
                    controllerLogE.setTime(time);
                    String err = "aop拦截到未知的异常: " + e.getMessage() + (object == null ? "" : ("|" + JSONObject.toJSONString(object))) + "\r\n" + "异常栈:\r\n" + getStackTrace(e);
                    controllerLogE.setExceptionDetail(err);
                    controllerLogE.setDone(false);
                    object = R.init().error(500, "拦截到异常: " + e.getClass() + "; 请联系工程师检查");
                    controllerLogE.setOutParam(JSONObject.toJSONString(object));
                    LOGGER.error("aop拦截到未知的异常", e);
                    //e.printStackTrace();
                    //
                }

                // 记录ip
                controllerLogE.setRequestIp(CusAccessObjectUtil.getIpAddress(httpServletRequest));
                // 保存进数据库
                if (controllerLogHandle != null) {
                    controllerLogHandle.add(controllerLogE);
                }
//                ControllerLogE finalControllerLogE = controllerLogE;
//                executor.execute(new Runnable() {
//                    @Override
//                    public void run() {
//                        if (controllerLogHandle != null) {
//                            controllerLogHandle.add(finalControllerLogE);
//                        }
//                    }
//                });
            } else {
                // 没有包含注解
                object = pjp.proceed();
            }
        } else {
            // 不需要拦截直接执行
            object = pjp.proceed();
        }
        return object;
    }

    /**
     * 分类,请求类型
     *
     * @param controllerLogE
     * @return
     */
    private ControllerLogE requestDispose(ControllerLogE controllerLogE) {
        // 请求url
        String requestURI = httpServletRequest.getRequestURI();
        String[] urls = requestURI.split("/");
        for (int i = 0; i < urls.length; i++) {
            if (!ControllerOperat.IsDefined(urls[i])) {
                continue;
            }
            ControllerOperat operat = ControllerOperat.from(urls[i]);
            controllerLogE.setLogType(operat.name);
        }
        return controllerLogE;
    }

    public static String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);

        try {
            throwable.printStackTrace(pw);
            return sw.toString();
        } finally {
            pw.close();
        }
    }

}
